/*******************************************************************************
 * Venice Noise Android Application
 * Copyright (C) 2011  Worcester Polytechnic Institute, Wesley Ripley
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * If you plan to use or modify any of this code please contact Fabio Carrera (carrera.fabio@gmail.com)
 * Also please attribute any code used in the final product to the developers. 
 * 
 * Author: Wesley Ripley (wripley@wpi.edu) 
 *******************************************************************************/
package org.venicenoise.app;

import java.io.File;
import java.io.FileOutputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A utility class for dealing with images.
 * @author Wesley Ripley
 * @version 12/6/2011
 */
public class ImageUtil {
	
	/**
	 * Get the dimensions of a image file without loading the image into memory
	 * @param imageFile
	 * 		The file containing the image to get the dimensions of
	 * @return
	 * 		The dimensions of the image
	 */
	public static Dimension getImageFileDimensions(File imageFile)
	{
		return getImageFileDimensions(imageFile.getAbsolutePath());
	}
	
	/**
	 * Get the dimensions of a image file without loading the image into memory
	 * @param imageFilePath
	 * 		Absolute path to the image file
	 * @return
	 * 		The dimensions of the image
	 */
	public static Dimension getImageFileDimensions(String imageFilePath)
	{
		BitmapFactory.Options boundsOption = new BitmapFactory.Options();
		//inJustDecode bounds tells it to decode the file only to get the bounds
		//and not to load it into memory. The dimensions end up in options.outWidth and options.outHeight
		boundsOption.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(imageFilePath,boundsOption);
		return new Dimension(boundsOption.outWidth,boundsOption.outHeight);
	}
	
	/**
	 * Scale an image file to the given dimensions
	 * @param imageFile
	 * 		The file containing the image
	 * @param newDimension
	 * 		Dimension to scale image to
	 * @return
	 * 		A Bitmap of the scaled image
	 */
	public static Bitmap scaleImageFile(File imageFile, Dimension newDimension)
	{
		return scaleImageFile(imageFile.getAbsolutePath(),newDimension);
	}
	
	/**
	 * Scale an image file to the given dimensions
	 * @param imageFile
	 * 		Absolute path to image file
	 * @param newDimension
	 * 		Dimension to scale image to
	 * @return
	 * 		A Bitmap of the scaled image
	 */
	public static Bitmap scaleImageFile(String imageFilePath, Dimension newDimension)
	{
		return scaleImageFile(imageFilePath,getImageFileDimensions(imageFilePath),newDimension);
	}
	
	/**
	 * Scale an image file to the given dimensions, if you already know the dimensions of the image
	 * Used this for efficiency so the dimensions don't have to be found twice
	 * @param imageFile
	 * 		The file containing the image
	 * @param oldDimensions
	 * 		Dimensions of the given image
	 * @param newDimension
	 * 		Dimensions to scale image to
	 * @return
	 * 		A Bitmap of the scaled image
	 */
	public static Bitmap scaleImageFile(File imageFile, Dimension oldDimensions, Dimension newDimension)
	{
		return scaleImageFile(imageFile.getAbsolutePath(),oldDimensions,newDimension);
	}
	
	/**
	 * Scale an image file to the given dimensions, if you already know the dimensions of the image
	 * Used this for efficiency so the dimensions don't have to be found twice
	 * @param imageFilePath
	 * 		Absolute path to image file
	 * @param oldDimensions
	 * 		Dimensions of the given image
	 * @param newDimension
	 * 		Dimensions to scale image to
	 * @return
	 * 		A Bitmap of the scaled image
	 */
	public static Bitmap scaleImageFile(String imageFilePath, Dimension oldDimensions, Dimension newDimension)
	{
		//decode the file into memory at the smallest size that is still greater than the desired dimensions
		int sampleSize = maxInSampleSize(oldDimensions, newDimension);
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inSampleSize = sampleSize;
		options.inDither = false;
		Bitmap decodedImage = BitmapFactory.decodeFile(imageFilePath,options);
		//scale the bitmap to the exact size
		return Bitmap.createScaledBitmap(decodedImage, newDimension.getWidth(), newDimension.getHeight(), true  	);
	}
	
	/**
	 * Scale image so that its largest side is a certain size at maximum.
	 * Keeps aspect ratio of image. Does not scale up.
	 * @param imageFile
	 * 		The file containing the image
	 * @param maxSide
	 * 		The maximum length of the largest side after scaling
	 * @return
	 * 		A Bitmap of the Scaled image
	 */
	public static Bitmap scaleImageFile(File imageFile, int maxSide)
	{
		return scaleImageFile(imageFile.getAbsolutePath(),maxSide);
	}
	
	/**
	 * Scale image so that its largest side is a certain size at maximum.
	 * Keeps aspect ratio of image. Does not scale up.
	 * @param imageFilePath
	 * 		Absolute path to image file
	 * @param maxSide
	 * 		The maximum length of the largest side after scaling
	 * @return
	 * 		A Bitmap of the Scaled image
	 */
	public static Bitmap scaleImageFile(String imageFilePath, int maxSide)
	{
		Dimension dimension = getImageFileDimensions(imageFilePath);
		int width = dimension.getWidth();
		int height = dimension.getHeight();
		if(width>height)
		{
			if(width > maxSide) //only scale if its bigger than max
			{
				double ratio = (double)maxSide / (double)width;
				width = maxSide;
				height = (int)(height * ratio);
			}
		}
		else
		{
			if(height > maxSide)
			{
				double ratio = (double)maxSide / (double)height;
				height = maxSide;
				width = (int)(width * ratio);
			}
		}
		return scaleImageFile(imageFilePath,dimension,new Dimension(width,height));
	}
	
	/**
	 * Calculate the maximum BitmapFactory.Options inSampleSize value that will still
	 * get you a image larger than the targeted dimension
	 * @param oldDimension
	 * 		Current dimensions of image
	 * @param newDimension
	 * 		Target dimension for image
	 * @return
	 * 		Maximum inSampleSize value
	 */
	public static int maxInSampleSize(Dimension oldDimension, Dimension newDimension)
	{
		int maxRatio = Math.max(oldDimension.getWidth()/newDimension.getWidth(), oldDimension.getHeight()/newDimension.getHeight());
		if(maxRatio <= 1)
		{
			return 1;
		}
		return lastPowerOf2(maxRatio);
	}
	
	/**
	 * Write a Bitmap to a compressed image file
	 * @param image
	 * 		The Bitmap to write to disk
	 * @param dstFile
	 * 		The file the image should be written to
	 * @param format
	 * 		The format to be used to write and compress the image
	 * @param quality
	 * 		Hint to the compressor, 0-100. 0 meaning compress for small size, 
	 * 		100 meaning compress for max quality. Some formats, like PNG which is lossless, 
	 * 		will ignore the quality setting
	 * @return
	 * 		true if completed , false otherwise 
	 */
	public static boolean compressImageFile(Bitmap image, File dstFile, Bitmap.CompressFormat format, int quality)
	{
		FileOutputStream out;
		try 
		{
			out = new FileOutputStream(dstFile);
			image.compress(format, quality, out);
			out.flush();
			out.close();
		} catch (Exception e) {
			return false;
		}
		return true;
	}
	
	/**
	 * Find the closest power of 2 that is less than or equal to the given number.
	 * Ex. lastPowerOf2(3) = 2, lastPowerOf2(4) = 4, lastPowerOf2(7) = 4, lastPowerOf2(9) = 8
	 * @param num
	 * 		The number we trying to get the power of 2 closest to
	 * @return
	 * 		The closest power of 2 less than or equal to the given number
	 */
	private static int lastPowerOf2(int num)
	{
		int closestPower = (int)Math.floor(Math.log(num)/Math.log(2));
		return (int)Math.pow(2, closestPower);
	}
	
	/**
	 * A class representing the dimensions of an image
	 * @author Wesley Ripley
	 */
	public static class Dimension
	{
		private final int width;
		private final int height;
		
		/**
		 * Constructor for Dimension
		 * @param width
		 * 		The width of the image
		 * @param height
		 * 		The height of the image
		 */
		public Dimension(int width, int height)
		{
			this.width = width;
			this.height = height;
		}
		
		/**
		 * Get the width of this Dimension
		 * @return
		 * 		the width of this Dimension
		 */
		public int getWidth()
		{
			return width;
		}
		
		/**
		 * Get the height of this Dimension
		 * @return
		 * 		the height of this Dimension
		 */
		public int getHeight()
		{
			return height;
		}
	}
}
